package com.hh.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HtmlUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hh.common.DeleteRequest;
import com.hh.common.ErrorCode;
import com.hh.constant.CommonConstant;
import com.hh.constant.UserConstant;
import com.hh.entity.domain.*;
import com.hh.entity.dto.blog.*;
import com.hh.entity.enums.ArticleReviewStatusEnum;
import com.hh.entity.vo.BlogVO;
import com.hh.entity.vo.TagCount;
import com.hh.entity.vo.UserVO;
import com.hh.exception.BusinessException;
import com.hh.exception.ThrowUtils;
import com.hh.mapper.BlogMapper;
import com.hh.service.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author hanhui
 * @description 针对表【Blog】的数据库操作Service实现
 * @createDate 2025-03-09 13:51:09
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog>
        implements BlogService {

    private final ElasticsearchRestTemplate elasticsearchRestTemplate;
    private final UserService userService;
    private final BlogMapper blogMapper;
    private final ThumbService thumbService;
    private final UserCollectionService collectionService;
    private final FollowService followService;
    private final RedisTemplate<String, Object> redisTemplate;
    // 权重配置
    private static final double VIEW_WEIGHT = 1.0;
    private static final double LIKE_WEIGHT = 2.0;
    private static final double COLLECT_WEIGHT = 3.0;

    /**
     * 查询文章列表
     *
     * @param blogReviewRequest 博客审核请求
     * @param loginUser         登录用户
     */
    @Override
    public void doBlogReview(BlogReviewRequest blogReviewRequest, User loginUser) {
        Long id = blogReviewRequest.getId();
        Integer reviewStatus = blogReviewRequest.getReviewStatus();
        ArticleReviewStatusEnum reviewStatusEnum = ArticleReviewStatusEnum.getEnumByValue(reviewStatus);
        if (id == null || reviewStatusEnum == null || ArticleReviewStatusEnum.REVIEWING.equals(reviewStatusEnum)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // 判断是否存在
        Blog oldBlog = this.getById(id);
        ThrowUtils.throwIf(oldBlog == null, ErrorCode.DATA_NULL_ERROR);
        // 已是该状态
        if (oldBlog.getReviewStatus().equals(reviewStatus)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "请勿重复审核");
        }
        // 更新审核状态
        Blog updateBlog = new Blog();
        BeanUtils.copyProperties(blogReviewRequest, updateBlog);
        updateBlog.setReviewerId(loginUser.getId());
        updateBlog.setReviewTime(new Date());
        boolean result = this.updateById(updateBlog);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
    }

    /**
     * 填充审核状态
     *
     * @param Blog      博客
     * @param loginUser 登录用户
     */
    @Override
    public void fillReviewParams(Blog Blog, User loginUser) {
        if (userService.isAdmin(loginUser)) {
            // 管理员自动过审
            Blog.setReviewStatus(ArticleReviewStatusEnum.PASS.getValue());
            Blog.setReviewerId(loginUser.getId());
            Blog.setReviewMessage("管理员自动过审");
            Blog.setReviewTime(new Date());
        } else {
            // 非管理员，创建或编辑都要改为待审核
            Blog.setReviewStatus(ArticleReviewStatusEnum.REVIEWING.getValue());
        }
    }

    /**
     * 发布
     *
     * @param blogAddRequest 添加文章请求
     * @param loginUser      登录用户
     * @return 文章
     */
    @Override
    public Blog addBlog(BlogAddRequest blogAddRequest, User loginUser) {
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR);
        ThrowUtils.throwIf(blogAddRequest == null, ErrorCode.PARAMS_NULL);
        String title = blogAddRequest.getTitle();
        String content = blogAddRequest.getContent();
        List<String> tags = blogAddRequest.getTags();
        String category = blogAddRequest.getCategory();

        if (title == null || title.isEmpty()) {
            throw new BusinessException(ErrorCode.NOTICE_TITLE_NULL, "文章标题不能为空");
        }
        if (content == null || content.isEmpty()) {
            throw new BusinessException(ErrorCode.NOTICE_CONTENT_NULL, "文章内容不能为空");
        }
        if (tags == null || tags.isEmpty()) {
            blogAddRequest.setTags(Collections.singletonList("默认标签"));
        }
        if (category == null || category.isEmpty()) {
            blogAddRequest.setCategory("默认分类");
        }
        Long userId = loginUser.getId();
        Blog Blog = new Blog();
        BeanUtils.copyProperties(blogAddRequest, Blog);
        Blog.setUserId(userId);
        Blog.setCreateTime(new Date());
        Blog.setTags(JSONUtil.toJsonStr(blogAddRequest.getTags()));
        this.fillReviewParams(Blog, loginUser);
        boolean save = this.save(Blog);
        ThrowUtils.throwIf(!save, ErrorCode.OPERATION_ERROR);
        return Blog;
    }

    /**
     * 更新
     *
     * @param blogUpdateRequest 更新文章请求
     * @param loginUser         登录用户
     */
    @Override
    public void updateBlog(BlogUpdateRequest blogUpdateRequest, User loginUser) {
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR);
        ThrowUtils.throwIf(blogUpdateRequest == null, ErrorCode.PARAMS_NULL);
        Long id = blogUpdateRequest.getId();
        ThrowUtils.throwIf(id == null, ErrorCode.PARAMS_NULL);
        Blog Blog = new Blog();
        BeanUtils.copyProperties(blogUpdateRequest, Blog);
        Blog.setTags(JSONUtil.toJsonStr(blogUpdateRequest.getTags()));
        this.fillReviewParams(Blog, loginUser);
        boolean update = this.updateById(Blog);
        ThrowUtils.throwIf(!update, ErrorCode.OPERATION_ERROR);
    }

    /**
     * 删除
     *
     * @param deleteRequest 删除请求
     * @param loginUser     登录用户
     */
    @Override
    public Boolean deleteBlog(DeleteRequest deleteRequest, User loginUser) {
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR);
        ThrowUtils.throwIf(deleteRequest == null, ErrorCode.PARAMS_NULL);
        Long id = deleteRequest.getId();
        ThrowUtils.throwIf(id == null, ErrorCode.PARAMS_NULL);
        Blog byId = this.getById(id);
        ThrowUtils.throwIf(byId == null, ErrorCode.FILE_NOT_FOUND);
        boolean success = false;
        if (userService.isAdmin(loginUser)) {
            //管理员可以删除任意文章
            success = this.removeById(id);
        } else {
            //非管理员只能删除自己发布的文章
            success = this.remove(
                    new QueryWrapper<Blog>()
                            .eq("userId", loginUser.getId())
                            .eq("id", id)
            );
        }
        ThrowUtils.throwIf(!success, ErrorCode.OPERATION_ERROR);
        return success;
    }

    /**
     * vo
     *
     * @param Blog 博客
     * @return
     */
    @Override
    public BlogVO getBlogVo(Blog Blog) {
        if (Blog == null) {
            return null;
        }
        BlogVO blogVO = BlogVO.objToVo(Blog);
        Long creatorId = Blog.getUserId();
        User user = userService.getById(creatorId);
        if (creatorId != null && creatorId > 0) {
            UserVO userVo = userService.getUserVo(user);
            //文章总数
            userVo.setBlogCount(this.count(new QueryWrapper<Blog>().eq("userId", creatorId)));
            //点赞总数
            userVo.setLikeCount(this.count(new QueryWrapper<Blog>().eq("userId", creatorId)));
            //粉丝量
            userVo.setFollowCount(followService.count(new QueryWrapper<Follow>().eq("followUserId", creatorId)));
            blogVO.setUser(userVo);
        }
        //是否点赞
        Boolean exist = thumbService.hasThumb(blogVO.getId(), userService.getUserVo(user).getId());
        blogVO.setHasThumb(exist);
        return blogVO;
    }

    /**
     * 获取分页封装类
     *
     * @param BlogPage
     * @return
     */
    @Deprecated
    public Page<BlogVO> getBlogVoPage1(Page<Blog> BlogPage, HttpServletRequest request) {
        // 获取当前登录用户
        User loginUser = userService.getLoginUser(request);
        // 创建一个Map，用于存储博客ID和点赞状态
        Map<Long, Boolean> blogIdHashThumbMap = new HashMap<>();
        Map<Long, Boolean> blogIdHashCollectMap = new HashMap<>();
        // 获取博客列表
        List<Blog> blogList = BlogPage.getRecords();
        // 创建一个分页封装类
        Page<BlogVO> BlogVOPage = new Page<>(BlogPage.getCurrent(), BlogPage.getSize(),
                BlogPage.getTotal());
        // 如果博客列表为空，直接返回分页封装类
        if (CollUtil.isEmpty(blogList)) {
            return BlogVOPage;
        }
        //用户登录
        //批量获取点赞数据
        if (ObjUtil.isNotEmpty(loginUser)) {
            // 获取博客ID列表
            List<Long> blogIdList = blogList.stream().map(Blog::getId).collect(Collectors.toList());

            // 从数据库获取点赞信息
            if (CollUtil.isNotEmpty(blogIdList)) {
                List<Long> thumbBlogIds = thumbService.list(new QueryWrapper<Thumb>()
                                .eq("userId", loginUser.getId())
                                .in("blogId", blogIdList)) // 假设type=0表示博客点赞
                        .stream()
                        .map(Thumb::getBlogId)
                        .collect(Collectors.toList());

                // 将点赞的博客ID存入Map
                for (Long blogId : thumbBlogIds) {
                    blogIdHashThumbMap.put(blogId, true);
                }
            }

            // 从数据库获取收藏信息
            if (CollUtil.isNotEmpty(blogIdList)) {
                // 这里需要根据你的收藏表结构来查询
                // 假设有一个Collect实体类和对应的Service
                List<Long> collectBlogIds = collectionService.list(new QueryWrapper<UserCollection>()
                                .eq("userId", loginUser.getId())
                                .in("articleId", blogIdList))
                        .stream()
                        .map(UserCollection::getBlogId)
                        .collect(Collectors.toList());

                // 将收藏的博客ID存入Map
                for (Long blogId : collectBlogIds) {
                    blogIdHashCollectMap.put(blogId, true);
                }
            }
        }
        // 将博客列表转换为博客VO列表
        List<BlogVO> blogVOList = blogList.stream().map(BlogVO::objToVo).collect(Collectors.toList());
        // 获取博客列表中的创建人ID集合
        Set<Long> creatorIdSet = blogList.stream().map(Blog::getUserId).collect(Collectors.toSet());
        // 根据创建人ID集合获取创建人列表，并按照创建人ID分组
        Map<Long, List<User>> userIdUserListMap = userService.listByIds(creatorIdSet).stream()
                .collect(Collectors.groupingBy(User::getId));
        //  填充创建人
        blogVOList.forEach(blogVO -> {
            Long creatorId = blogVO.getUserId();
            User user = null;
            // 如果创建人ID在Map中存在，则获取创建人
            if (userIdUserListMap.containsKey(creatorId)) {
                user = userIdUserListMap.get(creatorId).get(0);
            }
            UserVO userVo = userService.getUserVo(user);

            //文章总数
            userVo.setBlogCount(this.count(new QueryWrapper<Blog>().eq("userId", creatorId)));
            //点赞总数
            userVo.setLikeCount(this.count(new QueryWrapper<Blog>().eq("userId", creatorId)));
            //收藏总数
            userVo.setCollectCount(this.count(new QueryWrapper<Blog>().eq("userId", creatorId)));
            //粉丝量
            userVo.setFollowCount(followService.count(new QueryWrapper<Follow>().eq("followUserId", creatorId)));
            // 设置创建人
            blogVO.setUser(userVo);
            // 设置点赞状态
            blogVO.setHasThumb(blogIdHashThumbMap.get(blogVO.getId()) != null && blogIdHashThumbMap.get(blogVO.getId()));
            blogVO.setHasCollect(blogIdHashCollectMap.get(blogVO.getId()) != null && blogIdHashCollectMap.get(blogVO.getId()));
            //过滤标签
            blogVO.setContent(StrUtil.removeAll(blogVO.getContent(), '#', '*', '>'));
            blogVO.setContent(HtmlUtil.cleanHtmlTag(blogVO.getContent()));
        });
        // 设置分页封装类的记录
        BlogVOPage.setRecords(blogVOList);
        // 返回分页封装类
        return BlogVOPage;
    }

    @Override
    public Page<BlogVO> getBlogVoPage(Page<Blog> BlogPage, HttpServletRequest request) {
        // 获取当前登录用户
        Object userObj = request.getSession().getAttribute(UserConstant.USER_LOGIN_KEY_PREFIX);
        User loginUser = (User) userObj;
        // 创建一个Map，用于存储博客ID和点赞状态
        Map<Long, Boolean> blogIdHashThumbMap = new HashMap<>();
        Map<Long, Boolean> blogIdHashCollectMap = new HashMap<>();
        // 获取博客列表
        List<Blog> blogList = BlogPage.getRecords();
        // 创建一个分页封装类
        Page<BlogVO> BlogVOPage = new Page<>(BlogPage.getCurrent(), BlogPage.getSize(),
                BlogPage.getTotal());
        // 如果博客列表为空，直接返回分页封装类
        if (CollUtil.isEmpty(blogList)) {
            return BlogVOPage;
        }

        //用户登录
        //批量获取点赞数据
        if (ObjUtil.isNotEmpty(loginUser)) {
            // 获取博客ID列表
            List<Long> blogIdList = blogList.stream().map(Blog::getId).collect(Collectors.toList());

            // 从数据库获取点赞信息
            if (CollUtil.isNotEmpty(blogIdList)) {
                List<Long> thumbBlogIds = thumbService.list(new QueryWrapper<Thumb>()
                                .eq("userId", loginUser.getId())
                                .in("blogId", blogIdList)) // 假设type=0表示博客点赞
                        .stream()
                        .map(Thumb::getBlogId)
                        .collect(Collectors.toList());

                // 将点赞的博客ID存入Map
                for (Long blogId : thumbBlogIds) {
                    blogIdHashThumbMap.put(blogId, true);
                }
            }

            // 从数据库获取收藏信息
            if (CollUtil.isNotEmpty(blogIdList)) {
                // 这里需要根据你的收藏表结构来查询
                // 假设有一个Collect实体类和对应的Service
                List<Long> collectBlogIds = collectionService.list(new QueryWrapper<UserCollection>()
                                .eq("userId", loginUser.getId())
                                .in("articleId", blogIdList))
                        .stream()
                        .map(UserCollection::getBlogId)
                        .collect(Collectors.toList());

                // 将收藏的博客ID存入Map
                for (Long blogId : collectBlogIds) {
                    blogIdHashCollectMap.put(blogId, true);
                }
            }
        }

        // 将博客列表转换为博客VO列表
        List<BlogVO> blogVOList = blogList.stream().map(BlogVO::objToVo).collect(Collectors.toList());

        // 获取博客列表中的创建人ID集合
        Set<Long> creatorIdSet = blogList.stream().map(Blog::getUserId).collect(Collectors.toSet());

        // 根据创建人ID集合获取创建人列表，并按照创建人ID分组
        Map<Long, List<User>> userIdUserListMap = userService.listByIds(creatorIdSet).stream()
                .collect(Collectors.groupingBy(User::getId));

        // 批量获取统计信息
        Map<Long, Long> blogCountMap = this.countByUserIds(creatorIdSet, "userId");
        Map<Long, Long> likeCountMap = this.countByUserIds(creatorIdSet, "userId");
        Map<Long, Long> collectCountMap = this.countByUserIds(creatorIdSet, "userId");
//        Map<Long, Long> followCountMap = this.countByUserIds(creatorIdSet, "followUserId");

        //  填充创建人
        blogVOList.forEach(blogVO -> {
            Long creatorId = blogVO.getUserId();
            User user = null;
            // 如果创建人ID在Map中存在，则获取创建人
            if (userIdUserListMap.containsKey(creatorId)) {
                user = userIdUserListMap.get(creatorId).get(0);
            }
            UserVO userVo = userService.getUserVo(user);

            //文章总数
            userVo.setBlogCount(blogCountMap.getOrDefault(creatorId, 0L));
            //点赞总数
            userVo.setLikeCount(likeCountMap.getOrDefault(creatorId, 0L));
            //收藏总数
            userVo.setCollectCount(collectCountMap.getOrDefault(creatorId, 0L));
            //粉丝量
            //粉丝量
            userVo.setFollowCount(followService.count(new QueryWrapper<Follow>().eq("followUserId", creatorId)));

            // 设置创建人
            blogVO.setUser(userVo);
            // 设置点赞状态
            blogVO.setHasThumb(blogIdHashThumbMap.getOrDefault(blogVO.getId(), false));
            blogVO.setHasCollect(blogIdHashCollectMap.getOrDefault(blogVO.getId(), false));
            //过滤标签
            blogVO.setContent(StrUtil.removeAll(blogVO.getContent(), '#', '*', '>'));
            blogVO.setContent(HtmlUtil.cleanHtmlTag(blogVO.getContent()));
        });

        // 设置分页封装类的记录
        BlogVOPage.setRecords(blogVOList);
        // 返回分页封装类
        return BlogVOPage;
    }

    // 新增辅助方法：批量统计
    private Map<Long, Long> countByUserIds(Set<Long> userIds, String fieldName) {
        if (CollUtil.isEmpty(userIds)) {
            return new HashMap<>();
        }
        return this.list(new QueryWrapper<Blog>().in(fieldName, userIds))
                .stream()
                .collect(Collectors.groupingBy(
                        blog -> (Long) ReflectUtil.getFieldValue(blog, fieldName),
                        Collectors.counting()
                ));
    }

    /**
     * 获取热门文章列表（按浏览量排序）
     *
     * @param limit 返回的文章数量
     * @return 文章VO列表
     */
    @Override
    public List<BlogVO> getHotBlogs(int limit) {
        String redisKey = "blog:hot";
        // 从 Redis 获取热度最高的文章 ID 列表
        Set<Object> BlogIds = redisTemplate.opsForZSet().reverseRange(redisKey, 0, limit - 1);
        if (CollUtil.isEmpty(BlogIds)) {
            // 如果 Redis 中没有数据，则回退到数据库查询
            return fallbackToDatabase(limit);
        }
        // 查询文章详情并构建 VO
        return BlogIds.stream()
                .map(id -> {
                    Blog Blog = this.getById((Long) id);
                    return this.getBlogVo(Blog);
                })
                .collect(Collectors.toList());
    }

    /**
     * 回退到数据库查询（用于初始化或 Redis 数据为空时）
     */
    private List<BlogVO> fallbackToDatabase(int limit) {
        // 按热度排序（viewCount + likeCount + collectCount）
        List<Blog> Blogs = this.list(
                new QueryWrapper<Blog>().orderByDesc("(viewCount * " + VIEW_WEIGHT + ") + (likeCount * " + LIKE_WEIGHT + ") + (collectCount * " + COLLECT_WEIGHT + ")")
                        .last("LIMIT " + limit)
        );

        return Blogs.stream()
                .map(this::getBlogVo)
                .collect(Collectors.toList());
    }

    /**
     * 更新文章热度分数到 Redis
     *
     * @param BlogId 文章ID
     */
    @Override
    public void updateBlogHotScore(Long BlogId) {
        BlogVO blogVo = this.getBlogVo(this.getById(BlogId));
        if (blogVo == null) {
            return;
        }
        String redisKey = "blog:hot";
        double score = calculateHotScore(blogVo);

        // 更新 Redis 中的文章热度分数
        redisTemplate.opsForZSet().add(redisKey, BlogId, score);
    }

    /**
     * 计算文章热度分数
     */
    private double calculateHotScore(BlogVO blogVo) {
        return VIEW_WEIGHT * blogVo.getViewCount()
                + LIKE_WEIGHT * blogVo.getLikeCount()
                + COLLECT_WEIGHT * blogVo.getCollectCount();
    }


    @Override
    public Map<String, Integer> getTabCloud() {
        List<TagCount> tagCounts = blogMapper.selectTabs();
        // 统计每种标签的总数量
        return countTags(tagCounts);
    }

    /**
     * 统计所有标签的出现次数
     *
     * @param tagCounts 原始标签数据列表
     * @return 每个标签及其对应的总次数
     */
    private Map<String, Integer> countTags(List<TagCount> tagCounts) {
        // 使用HashMap存储标签及其计数
        Map<String, Integer> tagStatistics = new HashMap<>();

        // 遍历每个TagCount对象
        for (TagCount tagCount : tagCounts) {
            List<String> tags = tagCount.getTagName();
            int count = tagCount.getCount();

            // 遍历当前对象中的每个标签
            for (String tag : tags) {
                // 累加标签计数，若不存在则初始化为0后再加
                tagStatistics.put(tag, tagStatistics.getOrDefault(tag, 0) + count);
            }
        }

        // 将结果按数量降序排序
        List<Map.Entry<String, Integer>> entryList = new ArrayList<>(tagStatistics.entrySet());
        entryList.sort((entry1, entry2) -> entry2.getValue().compareTo(entry1.getValue()));

        // 转换为有序的LinkedHashMap
        Map<String, Integer> sortedResult = new LinkedHashMap<>();
        for (Map.Entry<String, Integer> entry : entryList) {
            sortedResult.put(entry.getKey(), entry.getValue());
        }

        return sortedResult;
    }

    /**
     * 获取查询构造器
     *
     * @param blogQueryRequest
     * @return
     */

    @Override
    public QueryWrapper<Blog> getQueryWrapper(BlogQueryRequest blogQueryRequest) {
        if (blogQueryRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_NULL);
        }
        Long id = blogQueryRequest.getId();
        String title = blogQueryRequest.getTitle();
        String content = blogQueryRequest.getContent();
        String searchText = blogQueryRequest.getSearchText();
        Long userId = blogQueryRequest.getUserId();
        List<String> tags = blogQueryRequest.getTags();
        String category = blogQueryRequest.getCategory();
        Date updateTime = blogQueryRequest.getUpdateTime();
        Date createTime = blogQueryRequest.getCreateTime();
        String sortField = blogQueryRequest.getSortField();
        String sortOrder = blogQueryRequest.getSortOrder();

        Integer reviewStatus = blogQueryRequest.getReviewStatus();
        String reviewMessage = blogQueryRequest.getReviewMessage();
        Long reviewerId = blogQueryRequest.getReviewerId();


        QueryWrapper<Blog> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(ObjUtil.isNotEmpty(id), "id", id);
        queryWrapper.like(title != null, "title", title);
        queryWrapper.like(content != null, "content", content);
        if (StrUtil.isNotBlank(searchText)) {
            queryWrapper.and(qw -> qw.like("title", searchText)
                    .or()
                    .like("content", searchText));
        }
        queryWrapper.eq(ObjUtil.isNotEmpty(userId), "userId", userId);
        //json数组查询
        if (CollUtil.isNotEmpty(tags)) {
            for (String tag : tags) {
                queryWrapper.like("tags", "\"" + tag + "\"");
            }
        }
        queryWrapper.like(category != null, "category", category);
        queryWrapper.eq(updateTime != null, "updateTime", updateTime);
        queryWrapper.eq(createTime != null, "createTime", createTime);
        queryWrapper.eq(reviewStatus != null, "reviewStatus", reviewStatus);
        queryWrapper.like(reviewMessage != null, "reviewMessage", reviewMessage);
        queryWrapper.eq(reviewerId != null, "reviewerId", reviewerId);
        queryWrapper.orderBy(StrUtil.isNotEmpty(sortField), sortOrder.equals("ascend"), sortField);
        return queryWrapper;
    }


    @Override
    public Page<BlogVO> searchFromEs(BlogQueryRequest blogQueryRequest) {
        Long id = blogQueryRequest.getId();
        String title = blogQueryRequest.getTitle();
        String content = blogQueryRequest.getContent();
        String searchText = blogQueryRequest.getSearchText();
        Long userId = blogQueryRequest.getUserId();
        List<String> tagList = blogQueryRequest.getTags();
        String category = blogQueryRequest.getCategory();
        int pageSize = blogQueryRequest.getPageSize();
        int pageNum = blogQueryRequest.getPageNum() - 1;
        String sortField = blogQueryRequest.getSortField();
        String sortOrder = blogQueryRequest.getSortOrder();

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //过滤
        boolQueryBuilder.filter(QueryBuilders.termQuery("isDelete", 0));
        boolQueryBuilder.filter(QueryBuilders.termQuery("reviewStatus", 1));
        if (id != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("id", id));
        }
        if (userId != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("userId", userId));
        }
        // 必须包含所有标签
        if (CollUtil.isNotEmpty(tagList)) {
            for (String tag : tagList) {
                boolQueryBuilder.filter(QueryBuilders.termQuery("tags", tag));
            }
        }
        // 按关键词检索
        if (StringUtils.isNotBlank(searchText)) {
            boolQueryBuilder.should(QueryBuilders.matchQuery("title", searchText));
            boolQueryBuilder.should(QueryBuilders.matchQuery("category", searchText));
            boolQueryBuilder.should(QueryBuilders.matchQuery("content", searchText));
            boolQueryBuilder.minimumShouldMatch(1);
        }
        // 按标题检索
        if (StringUtils.isNotBlank(title)) {
            boolQueryBuilder.should(QueryBuilders.matchQuery("title", title));
            boolQueryBuilder.minimumShouldMatch(1);
        }
        // 按内容检索
        if (StringUtils.isNotBlank(content)) {
            boolQueryBuilder.should(QueryBuilders.matchQuery("content", content));
            boolQueryBuilder.minimumShouldMatch(1);
        }
        //排序
        SortBuilder<?> sortBuilder = SortBuilders.scoreSort();
        if (StringUtils.isNotBlank(sortField)) {
            sortBuilder = SortBuilders.fieldSort(sortField);
            sortBuilder.order(CommonConstant.SORT_ORDER_ASC.equals(sortOrder) ? SortOrder.ASC : SortOrder.DESC);
        }
        //分页
        PageRequest pageRequest = PageRequest.of(pageNum, pageSize);
        //构造查询
        NativeSearchQuery searchQueryBuilder = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder)
                .withPageable(pageRequest).withSorts(sortBuilder)
                .withHighlightFields(  // 高亮字段显示
                        new HighlightBuilder.Field("content").preTags("<em style='color:red'>").postTags("</em>"),
                        new HighlightBuilder.Field("title").preTags("<em style='color:red'>").postTags("</em>")
                )
                .build();
        SearchHits<PostEsDTO> searchHits = elasticsearchRestTemplate.search(searchQueryBuilder, PostEsDTO.class);
        Page<BlogVO> page = new Page<>();
        page.setTotal(searchHits.getTotalHits());
        List<BlogVO> resourceList = new ArrayList<>();
        // 查出结果后，从 db 获取最新动态数据（比如点赞数）
        if (searchHits.hasSearchHits()) {
            List<SearchHit<PostEsDTO>> searchHitList = searchHits.getSearchHits();
            Map<Long, String> postIdTitleHighlightMap = new HashMap<>();
            Map<Long, String> postIdContentHighlightMap = new HashMap<>();
            searchHitList.stream().forEach(searchHit -> {
                Long postId = searchHit.getContent().getId();
                List<String> titleHighlightList = searchHit.getHighlightField("title");
                if (titleHighlightList.size() != 0) {
                    String titleHighlight = titleHighlightList.get(0);
                    if (StringUtils.isNotBlank(titleHighlight)) {
                        postIdTitleHighlightMap.put(postId, titleHighlight);
                    }
                }
                List<String> contentHighlightList = searchHit.getHighlightField("content");
                if (contentHighlightList.size() != 0) {
                    String contentHighlight = contentHighlightList.get(0);
                    if (StringUtils.isNotBlank(contentHighlight)) {
                        postIdContentHighlightMap.put(postId, contentHighlight);
                    }
                }
            });
            List<Long> postIdList = searchHitList.stream().map(searchHit -> searchHit.getContent().getId())
                    .collect(Collectors.toList());
            List<Blog> postList = baseMapper.selectBatchIds(postIdList);
            if (postList != null) {
                Map<Long, List<Blog>> idPostMap = postList.stream().collect(Collectors.groupingBy(Blog::getId));
                postIdList.forEach(postId -> {
                    if (idPostMap.containsKey(postId)) {
                        Blog post = idPostMap.get(postId).get(0);
                        String titleField = postIdTitleHighlightMap.get(postId);
                        if (StringUtils.isNotBlank(titleField)) {
                            post.setTitle(titleField);
                        }
                        String contentField = postIdContentHighlightMap.get(postId);
                        if (StringUtils.isNotBlank(contentField)) {
                            post.setContent(contentField);
                        }
                        resourceList.add(BlogVO.objToVo(post));

                    } else {
                        // 从 es 清空 db 已物理删除的数据
                        String delete = elasticsearchRestTemplate.delete(String.valueOf(postId), PostEsDTO.class);
                        log.info("delete post {}", delete);
                    }
                });
            }
        }
        page.setRecords(resourceList);
        return page;
    }

    @Override
    public List<Blog> listByUpdateTimeAfter(long timestamp) {
        Date date = new Date(timestamp);
        return this.list(new QueryWrapper<Blog>().gt("updateTime", date));
    }
}